﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Xml;

namespace editor.cfg
{
    public abstract class DData : INotifyPropertyChanged
    {
        public TType DType { get; private set; }

        public bool IsExpander { get; set; }

        public Field Field { get; set; }

        public string Title { get { return ""; } }

        public virtual DData Parent { get; set; }

        public DData Root { get { return Parent != null ? Parent.Root : this; } }

        public DData(TType type)
        {
            DType = type;
            IsExpander = true;
        }

        public abstract UIElement ToContent();
        public abstract DData Clone();

        public event Action AnyChange;

        public void OnAnyChange()
        {
            if (AnyChange != null)
                AnyChange();
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, e);

            if (Root != this)
                Root.OnAnyChange();
        }

        public void TriggerChange()
        {
            OnPropertyChanged(new PropertyChangedEventArgs("Value"));
        }

        public abstract string Content2String();

        public abstract void WriteXml(XmlWriter writter);
    }


    public abstract class DPrimity<T> : DData
    {
        public DPrimity(TType type, T value) : base(type)
        {
            Value = value;
        }


        protected T _value;
        public virtual T Value
        {
            get { return _value; }
            set { _value = value; OnPropertyChanged(new PropertyChangedEventArgs("Value")); }
        }

        public override string Content2String()
        {
            return Value.ToString();
        }

        public override void WriteXml(XmlWriter writter)
        {
            writter.WriteString(Value.ToString());
        }
    }

    public class DBool : DPrimity<bool>
    {
        public DBool(TBool type, bool value) : base(type, value)
        {
        }

        public override DData Clone()
        {
            return new DBool(DType as TBool, Value);
        }

        public override UIElement ToContent()
        {
            var o = new CheckBox() { MinWidth = 100, IsTabStop = true, VerticalAlignment = VerticalAlignment.Center };
            BindingOperations.SetBinding(o, CheckBox.IsCheckedProperty, new Binding { Source = this, Path = new PropertyPath("Value") });
            return o;
        }
    }

    public class DInt : DPrimity<int>
    {
        public DInt(TInt type, int x) : base(type, x)
        {
        }

        public override DData Clone()
        {
            return new DInt(DType as TInt, Value);
        }

        public override UIElement ToContent()
        {
            var o = new TextBox() { MinWidth = 100 };
            BindingOperations.SetBinding(o, TextBox.TextProperty, new Binding { Source = this, Path = new PropertyPath("Value"), UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
            return o;
        }
    }

    public class DLong : DPrimity<long>
    {
        public DLong(TLong type, long value) : base(type, value)
        {
        }

        public override DData Clone()
        {
            return new DLong(DType as TLong, Value);
        }

        public override UIElement ToContent()
        {
            var o = new TextBox() { MinWidth = 100 };
            BindingOperations.SetBinding(o, TextBox.TextProperty, new Binding { Source = this, Path = new PropertyPath("Value"), UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
            return o;
        }
    }


    public class DFloat : DPrimity<string>
    {
        public DFloat(TFloat type, string value) : base(type, value)
        {
        }

        public override DData Clone()
        {
            return new DFloat(DType as TFloat, Value);
        }

        public override UIElement ToContent()
        {
            var o = new TextBox() { MinWidth = 100 };
            BindingOperations.SetBinding(o, TextBox.TextProperty, new Binding { Source = this, Path = new PropertyPath("Value"), UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
            return o;
        }
    }

    public class DString : DPrimity<string>
    {
        public DString(TString type, string value) : base(type, value)
        {
        }

        public override DData Clone()
        {
            return new DString(DType as TString, Value);
        }

        public override UIElement ToContent()
        {
            var o = new TextBox() { MinWidth = 100, TextWrapping = TextWrapping.Wrap, AcceptsReturn = true, MaxLines = 10, MaxWidth = 500 };
            BindingOperations.SetBinding(o, TextBox.TextProperty, new Binding { Source = this, Path = new PropertyPath("Value"), UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
            return o;
        }
    }

    public class DEnum : DPrimity<string>
    {
        private readonly List<string> _cases;
        public DEnum(TEnum type, string value) : base(type, value)
        {
            this._cases = type.Type.Cases;
            if (!_cases.Contains(value))
            {
                var newValue = type.Type.ToFirstAliasNameOrDefault(value);
                Log.Info("value:{0} isn't valid enum:{1}. set to default:{2}", value, type.Type.FullName, newValue);
                Value = newValue;
            }
        }

        public override DData Clone()
        {
            return new DEnum(DType as TEnum, Value);
        }

        public override UIElement ToContent()
        {
            var o = new ComboBox() { MinWidth = 100, Background = Brushes.White };
            o.SelectedItem = Value;
            o.ItemsSource = (DType as TEnum).Type.Cases;
            o.PreviewMouseWheel += (sender, e) =>
            {
                DynamicBeanControl.InScroll = o.IsDropDownOpen;
            };

            o.SelectionChanged += (sender, e) =>
            {
                Value = o.SelectedItem as string;
            };
            return o;
        }
    }

    public class DBean : DPrimity<List<DData>>
    {
        public const int MAX_DEFAULT_EXPAND_FIELD_NUM = 5;
        public DBean(TBean type, List<DData> fields) : base(type, fields)
        {
            if (type.BeanType.IsDynamic)
                throw new Exception($"DBean type:{type.BeanType.FullName} should be static");
            IsExpander = fields.Count <= MAX_DEFAULT_EXPAND_FIELD_NUM;
            fields.ForEach(f => f.Parent = this);
            fields.Sort((a, b) => a.Field.ShowOrder - b.Field.ShowOrder);
        }

        public override DData Clone()
        {
            var o = new DBean(DType as TBean, Value.Select(f =>
            {
                var b = f.Clone();
                b.Field = f.Field;
                return b;
            }).ToList());
            o.Value.ForEach(f => f.Parent = o);
            return o;
        }

        public DData GetFieldByname(string name)
        {
            return Value.First(f => f.Field.Name == name);
        }

        public override UIElement ToContent()
        {
            if (Value.Count != 1)
                return new BeanControl(this);
            var vtype = (DType as TBean).BeanType.HierarchyFields[0].FType;
            if (!(vtype is TList || vtype is TSet || vtype is TMap))
                return new BeanControl(this);
            return new OneFieldBeanControl(this);
        }

        public override void WriteXml(XmlWriter writter)
        {
            writter.WriteAttributeString("type", (DType as TBean).BeanType.SimpleName);
            foreach (var f in Value)
            {
                writter.WriteStartElement(f.Field.Name);
                //writter.WriteAttributeString("id", f.Field.Id.ToString());
                f.WriteXml(writter);
                writter.WriteEndElement();
            }
        }
    }

    public class DDynamicBean : DPrimity<DBean>
    {
        public List<string> Classes { get; private set; }
        public List<string> Descs { get; private set; }

        public int CurTypeIndex { get { return Classes.IndexOf((Value.DType as TBean).BeanType.SimpleName); } }
        public string CurTypeSimpleName { get { return (Value.DType as TBean).BeanType.SimpleName; } }
        public string CurTypeDescName { get { return (Value.DType as TBean).BeanType.DescName; } }

        public DDynamicBean(TBean type, DBean value) : base(type, value)
        {
            if (!type.BeanType.IsDynamic)
                throw new Exception($"DDynamicBean type:{type.BeanType.FullName} should be dynamic");
            //IsExpander = false;
            IsExpander = value.Value.Count <= DBean.MAX_DEFAULT_EXPAND_FIELD_NUM;

            var childrenBean = type.BeanType.Children;
            childrenBean.Sort((b1, b2) => b1.SimpleName.CompareTo(b2.SimpleName));

            Classes = childrenBean.Select(c => c.SimpleName).ToList();
            Descs = childrenBean.Select(c => c.DescName).ToList();

            _value.Parent = this;
        }



        public override DBean Value
        {
            get { return _value; }
            set
            {
                _value = value;
                _value.Parent = this;
                OnPropertyChanged(new PropertyChangedEventArgs("Value"));
            }
        }

        public DBean NewData(string childTypeName)
        {
            var child = (DType as TBean).BeanType.Children.First(c => c.SimpleName == childTypeName);
            return new TBean(child, new Dictionary<string, string>()).NewDefault() as DBean;
        }

        public override DData Clone()
        {
            var o = new DDynamicBean(DType as TBean, Value.Clone() as DBean);
            return o;
        }

        public override UIElement ToContent()
        {
            return new DynamicBeanControl(this);
        }

        public override void WriteXml(XmlWriter writter)
        {
            writter.WriteAttributeString("type", CurTypeSimpleName);
            foreach (var f in Value.Value)
            {
                writter.WriteStartElement(f.Field.Name);
                //writter.WriteAttributeString("id", f.Field.Id.ToString());
                f.WriteXml(writter);
                writter.WriteEndElement();
            }
        }
    }

    public class DList : DPrimity<ObservableCollection<DData>>
    {
        public DList(TList type, ObservableCollection<DData> value) : base(type, value)
        {
            foreach (var f in value)
            {
                f.Parent = this;
            }
            IsExpander = false;
        }

        public override DData Clone()
        {
            var o = new DList(DType as TList, new ObservableCollection<DData>(Value.Select(d => d.Clone()).ToList()));
            o.IsExpander = IsExpander;
            foreach (var f in o.Value)
            {
                f.Parent = this;
            }
            return o;
        }

        public override UIElement ToContent()
        {
            var vtype = (DType as TList).ValueType;
            if (vtype.IsPrimitive())
            {
                return new PrimitiveListControl(this);
            } /*if(vtype is TBean)
            {
                var btype = vtype as TBean;
                if(btype.BeanType.IsDynamic || btype.BeanType.Fields.Count <= 3)
                {
                    return new PrimitiveListControl(this);
                }
            }*/
            return new ListControl(this);
        }

        public override void WriteXml(XmlWriter writter)
        {
            foreach (var f in Value)
            {
                writter.WriteStartElement("item");
                f.WriteXml(writter);
                writter.WriteEndElement();
            }
        }

        public void Insert(int index, DData data)
        {
            Value.Insert(index, data);
            data.Parent = this;
            TriggerChange();
        }

        public void Add(DData data)
        {
            Value.Add(data);
            data.Parent = this;
            TriggerChange();
        }

        public void RemoveAt(int index)
        {
            Value.RemoveAt(index);
            TriggerChange();
        }
    }

    public class DSet : DPrimity<ObservableCollection<DData>>
    {
        public DSet(TSet type, ObservableCollection<DData> value) : base(type, value)
        {
            foreach (var f in value)
            {
                f.Parent = this;
            }
            IsExpander = false;
        }

        public override DData Clone()
        {
            var o = new DSet(DType as TSet, new ObservableCollection<DData>(Value.Select(d => d.Clone()).ToList()));
            o.IsExpander = IsExpander;
            foreach (var f in o.Value)
            {
                f.Parent = this;
            }
            return o;
        }

        public override UIElement ToContent()
        {
            return new SetControl(this);
        }

        public override void WriteXml(XmlWriter writter)
        {
            foreach (var f in Value)
            {
                writter.WriteStartElement("item");
                f.WriteXml(writter);
                writter.WriteEndElement();
            }
        }

        public void Insert(int index, DData data)
        {
            Value.Insert(index, data);
            data.Parent = this;
            TriggerChange();
        }

        public void Add(DData data)
        {
            Value.Add(data);
            data.Parent = this;
            TriggerChange();
        }

        public void RemoveAt(int index)
        {
            Value.RemoveAt(index);
            TriggerChange();
        }
    }

    public class DMap : DPrimity<ObservableCollection<KeyValuePair<DData, DData>>>
    {
        public DMap(TType type, ObservableCollection<KeyValuePair<DData, DData>> value) : base(type, value)
        {
            foreach (var f in value)
            {
                f.Key.Parent = this;
                f.Value.Parent = this;
            }
            IsExpander = value.Count > 0;
        }

        public override DData Clone()
        {
            var o = new DMap(DType as TMap,
                new ObservableCollection<KeyValuePair<DData, DData>>(
                    Value.Select(d => new KeyValuePair<DData, DData>(d.Key.Clone(), d.Value.Clone())).ToList()));
            o.IsExpander = IsExpander;
            foreach (var f in o.Value)
            {
                f.Key.Parent = this;
                f.Value.Parent = this;
            }
            return o;
        }

        public override UIElement ToContent()
        {
            return new MapControl(this);
        }

        public override void WriteXml(XmlWriter writter)
        {
            foreach (var f in Value)
            {
                writter.WriteStartElement("item");

                writter.WriteStartElement("key");
                f.Key.WriteXml(writter);
                writter.WriteEndElement();

                writter.WriteStartElement("value");
                f.Value.WriteXml(writter);
                writter.WriteEndElement();

                writter.WriteEndElement();
            }
        }

        public void Insert(int index, DData key, DData value)
        {
            Value.Insert(index, new KeyValuePair<DData, DData>(key, value));
            key.Parent = this;
            value.Parent = this;
            TriggerChange();
        }

        public void Add(DData key, DData value)
        {
            Value.Add(new KeyValuePair<DData, DData>(key, value));
            key.Parent = this;
            value.Parent = this;
            TriggerChange();
        }

        public void RemoveAt(int index)
        {
            Value.RemoveAt(index);
            TriggerChange();
        }
    }
}
